/* Copyright (c) 2006 by M Aamir Maniar 
 * 
 * Permission is hereby granted, free of charge, to any person obtaining 
 * a copy of this software and associated documentation files (the 
 * "Software"), to deal in the Software without restriction, including 
 * without limitation the rights to use, copy, modify, merge, publish, 
 * distribute, sublicense, and/or sell copies of the Software, and to 
 * permit persons to whom the Software is furnished to do so, subject to 
 * the following conditions:
 * 
 * The above copyright notice and this permission notice shall be 
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 * */

namespace Generic.DataStructures
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using Generic.Patterns;
    using Generic.Utils;

    public class Matrix<T> : IEnumerable<T>, ICloneable, IEquatable<Matrix<T>>
    {
        private T[,] _array;

        #region public Matrix(int rows, int columns)
        /// <summary>
        /// Initializes a new <see cref="Matrix&lt;T&gt;"/> of the <see cref="Matrix&lt;T&gt;"/> class.
        /// </summary>
        /// <param name="rows">The rows.</param>
        /// <param name="columns">The columns.</param>
        public Matrix(int rows, int columns)
        {
            _array = new T[rows, columns];
            _rows = rows;
            _columns = columns;
        }
        #endregion

        #region public T this[int row, int column]
        /// <summary>
        /// Gets or sets the <see cref="T"/> with the specified row and column.
        /// </summary>
        /// <value></value>
        public T this[int row, int column]
        {
            get
            {
                return _array[row,column];
            }
            set
            {
                _array[row,column] = value;
            }
        }
        #endregion

        #region IEnumerable<T> Members

        public IEnumerator<T> GetEnumerator()
        {
            for (int rowIndex = 0; rowIndex < _rows; rowIndex++)
            {
                for (int colIndex = 0; colIndex < _columns; colIndex++)
                {
                    yield return _array[rowIndex, colIndex];
                }
            }
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region public int Rows
        private int _rows;
        /// <summary>
        /// Gets the rows.
        /// </summary>
        /// <value>The rows.</value>
        public int Rows
        {
            get { return _rows; }
        }
        #endregion

        #region public int Columns
        private int _columns;
        /// <summary>
        /// Gets the columns.
        /// </summary>
        /// <value>The columns.</value>
        public int Columns
        {
            get { return _columns; }
        }
        #endregion

        #region public bool IsSquare
        /// <summary>
        /// Gets a value indicating whether this <see cref="Matrix&lt;T&gt;"/> is square.
        /// </summary>
        /// <value><c>true</c> if this <see cref="Matrix&lt;T&gt;"/> is square; otherwise, <c>false</c>.</value>
        public bool IsSquare
        {
            get { return _rows == _columns; }
        }
        #endregion

        #region public void Clear()
        /// <summary>
        /// Clears <see cref="Matrix&lt;T&gt;"/>.
        /// </summary>
        public void Clear()
        {
            _array = new T[_rows, _columns];
        }
        #endregion

        #region public bool Contains(T item)
        /// <summary>
        /// Determines whether [contains] [the specified item].
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns>
        /// 	<c>true</c> if [contains] [the specified item]; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(T item)
        {
            foreach (T element in this)
            {
                if (element.Equals(item))
                    return true;
            }
            return false;
        }
        #endregion

        #region public Matrix<T> Transpose()
        /// <summary>
        /// Transposes the Matrix.
        /// </summary>
        /// <returns></returns>
        public Matrix<T> Transpose()
        {
            Matrix<T> newMatrix = new Matrix<T>(_columns, _rows);
            for (int row = 0; row < _rows; row++)
                for (int col = 0; col < _columns; col++)
                    newMatrix[col, row] = this[row, col];
            return newMatrix;
        }
        #endregion

        #region public override bool Equals(object obj)
        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <param name="obj">The <see cref="T:System.Object"></see> to compare with the current <see cref="T:System.Object"></see>.</param>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"></see> is equal to the current <see cref="T:System.Object"></see>; otherwise, false.
        /// </returns>
        public override bool Equals(object obj)
        {
            return Equals(obj);
        }
        #endregion

        #region IEquatable<T> Members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// true if the current object is equal to the other parameter; otherwise, false.
        /// </returns>
        public bool Equals(Matrix<T> other)
        {
            if (!IsDimensionSame(other))
                return false;

            for (int row = 0; row < _rows; row++)
                for (int col = 0; col < _columns; col++)
                    if (!this[row, col].Equals(other[row, col]))
                        return false;
            return true;
        }
        #endregion

        #region public bool IsDimensionSame(Matrix<T> other)
        /// <summary>
        /// Determines whether [is dimension same] [the specified other].
        /// </summary>
        /// <param name="other">The other.</param>
        /// <returns>
        /// 	<c>true</c> if [is dimension same] [the specified other]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsDimensionSame(Matrix<T> other)
        {
            return (_rows == other._rows) && (_columns == other._columns);
        }
        #endregion 

        #region public override int GetHashCode()
        /// <summary>
        /// Serves as a hash function for a particular type.
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override int GetHashCode()
        {
            return _array.GetHashCode();
        }
        #endregion

        #region public override string ToString()
        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            StringBuilder matrixBuilder = new StringBuilder();
            for (int row = 0; row < _rows; row++)
            {
                for (int col = 0; col < _columns; col++)
                {
                    matrixBuilder.AppendFormat("{0}\t", this[row, col].ToString());
                }
                matrixBuilder.Append(Environment.NewLine);
            }
            return matrixBuilder.ToString();
        }
        #endregion

        #region public string ToString(string columnSeperator)
        /// <summary>
        /// Toes the string.
        /// </summary>
        /// <param name="columnSeperator">The column seperator.</param>
        /// <returns></returns>
        public string ToString(string columnSeperator)
        {
            StringBuilder matrixBuilder = new StringBuilder();
            for (int row = 0; row < _rows; row++)
            {
                for (int col = 0; col < _columns; col++)
                {
                    matrixBuilder.AppendFormat("{0}{1}", this[row, col].ToString(), columnSeperator);
                }
                matrixBuilder.Append(Environment.NewLine);
            }
            return matrixBuilder.ToString();
        }
        #endregion

        #region public static bool operator ==(Matrix<T> rhs, Matrix<T> lhs)
        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="rhs">The RHS.</param>
        /// <param name="lhs">The LHS.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator ==(Matrix<T> rhs, Matrix<T> lhs)
        {
            return rhs.Equals(lhs);
        }
        #endregion

        #region public static bool operator !=(Matrix<T> rhs, Matrix<T> lhs)
        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="rhs">The RHS.</param>
        /// <param name="lhs">The LHS.</param>
        /// <returns>The result of the operator.</returns>
        public static bool operator !=(Matrix<T> rhs, Matrix<T> lhs)
        {
            return !(rhs.Equals(lhs));
        }
        #endregion

        #region public static Matrix<T> operator +(Matrix<T> rhs, Matrix<T> lhs)
        /// <summary>
        /// Perform addition operation on the specified Matrices.
        /// </summary>
        /// <param name="rhs">The RHS.</param>
        /// <param name="lhs">The LHS.</param>
        /// <returns>The result of the addition in to new Matrix.</returns>
        public static Matrix<T> operator +(Matrix<T> rhs, Matrix<T> lhs)
        {
            if (!Operator<T>.CanAdd)
                throw new InvalidOperationException(string.Format("Type {0} doesn't support addition.", rhs.GetType().FullName));

            if (!rhs.IsDimensionSame(lhs))
                throw new InvalidOperationException("Dimension differs in both the Matrices.");

            Matrix<T> newMatrix = new Matrix<T>(rhs.Rows, rhs.Columns);
            for (int row = 0; row < rhs._rows; row++)
                for (int col = 0; col < rhs._columns; col++)
                    newMatrix[row, col] = Operator<T>.Add(rhs[row, col], lhs[row, col]);
            return newMatrix;
        }
        #endregion

        #region public static Matrix<T> operator- (Matrix<T> rhs, Matrix<T> lhs)
        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="rhs">The RHS.</param>
        /// <param name="lhs">The LHS.</param>
        /// <returns>The result of the operator.</returns>
        public static Matrix<T> operator- (Matrix<T> rhs, Matrix<T> lhs)
        {
            if (!Operator<T>.CanSubstract)
                throw new InvalidOperationException(string.Format("Type {0} doesn't support substraction.", rhs.GetType().FullName));

            if (!rhs.IsDimensionSame(lhs))
                throw new InvalidOperationException("Dimension differs in both the Matrices.");

            Matrix<T> newMatrix = new Matrix<T>(rhs.Rows, rhs.Columns);
            for (int row = 0; row < rhs._rows; row++)
                for (int col = 0; col < rhs._columns; col++)
                    newMatrix[row, col] = Operator<T>.Substract(rhs[row, col], lhs[row, col]);
            return newMatrix;
        }
        #endregion

        #region public Matrix<T> Clone()
        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        public Matrix<T> Clone()
        {
            return Prototype<Matrix<T>>.Clone(this);
        }
        #endregion 

        #region ICloneable Members

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        object ICloneable.Clone()
        {
            return Clone();
        }

        #endregion

    }
}
